http-proxy hang up问题
September 06, 2019
最近项目有个新的需求,需要程序能够屏蔽某个用户,由于前段有load-balacer的缘故,原本的客户ip地址已经拿不到。所以采用的办法就是根据用户登录用的token,解密后来确定身份,进而决定屏蔽与否,如果需要屏蔽,就不再重定向的代理服务器,直接返回403,并给出客户联系方式。
问题
原来的结构
// http server
var server = https.createServer(credentials, requestListener).listen(port, listen);
// http proxy server
var proxyServer = httpProxy.createProxyServer(proxyHelper.options);
// requestListener function
function requestListener(req, res) {
...
if (req.method === 'POST' || req.method === 'PUT') {
var body = [];
var msgBody = '';
var msgFullBody = '';
req.on('data', function (chunk) {
body.push(chunk);
})
.on('end', function () {
body = Buffer.concat(body).toString();
if (JSON.stringify(body).toUpperCase().indexOf('PASSWORD') === -1) {
msgBody = 'BODY: ' + JSON.stringify(body).slice(0, 128);
msgFullBody = 'BODY: ' + JSON.stringify(body);
} else {
msgBody = 'BODY: Request body redacted because it contains sensitive password information.';
msgFullBody = 'BODY: Request body redacted because it contains sensitive password information.';
}
logger.info(msgBody);
requestLogger.info(msgFullBody);
});
}
...
proxyServer.web(req, res, { target: target }, function(err) {
if (err) {
logger.error('Error! ROUTER: proxy request failed', err.stack);
jsonLogger.error('Proxy Request Failed', makeJsonMessage(jsonHeader, {stack: err.stack}));
res.writeHead(500);
return res.end(err.message);
}});
}
应该说结构比较清楚,就是在代理之前,如果是POST
的请求,通过 on('data')
的callback来把内容打印出来。现在由于我们要先判别用户的身份再决定是否转向代理服务器,所以需要加一个判断:
function requestListener(req, res) {
...
var token = extractToken(req);
// try to decode the user to decide if block it or not as we cannot get ip address due to load balancer.
if (token) {
xxx.decodeToken(token)
.then(function (user) {
var id = user.user_name || user.client_id;
logger.info('user ID: ' + id);
if(blockedIdsCache.includes(id)){
res.writeHead(403);
res.end('You are not allowed to access. Please contact xxx');
logger.info('block this user: ' + id);
} else {
makeProxyRequest(req, res, target, jsonHeader);
}
})
.catch(function(err) {
var msg = 'Check user failed: ' + JSON.stringify(err.message);
logger.error(msg);
res.writeHead(500);
res.end(msg);
});
} else {
makeProxyRequest(req, res, target, jsonHeader);
}
}
function makeProxyRequest(req, res, target, jsonHeader){
proxyServer.web(req, res, { target: target }, function(err) {
if (err) {
logger.error('Error! ROUTER: proxy request failed', err.stack);
jsonLogger.error('Proxy Request Failed', makeJsonMessage(jsonHeader, {stack: err.stack}));
res.writeHead(500);
return res.end(err.message);
}});
}
问题出来了, GET完全没有问题,每次POST的时候,就迟迟不能转向代理服务器,直到超时,最后就打印出 hang up
error: [2019-09-05 15:24:59.051][][XXX][pid: 26956]: Error! ROUTER: proxy request failed Error: socket hang up
at createHangUpError (_http_client.js:268:15)
at TLSSocket.socketOnEnd (_http_client.js:360:23)
at emitNone (events.js:91:20)
at TLSSocket.emit (events.js:185:7)
at endReadableNT (_stream_readable.js:978:12)
at _combinedTickCallback (internal/process/next_tick.js:80:11)
at process._tickCallback (internal/process/next_tick.js:104:9)
解决方案1
经过测试发现,问题就出在 req.on('data', function (chunk) {...}
上面,这个callback的主要目的就是打印log,可是原始的req挂了这么个钩子后,http-proxy就不答应代理了。最简单的办法就是直接删掉这个钩子,可是我们又真的想要打印出来这个信息,怎么办?
解决方案2
经过多方搜索,终于找到一个解决办法,就是把这个钩子挂到代理里边去。
proxyServer.on('proxyReq', function(proxyReq, req, res, options){
if (req.method === 'POST' || req.method === 'PUT') {
var body = [];
var msgBody = '';
var msgFullBody = '';
req.on('data', function (chunk) {
body.push(chunk);
})
.on('end', function () {
...
});
}
});
这样代理就没问题,可是为何之前没加if判断的时候,怎么就没有问题呢,我现在的观察和答案是时间延迟。没加if判断的时候,代理是一步到位,直接上去的,也就是说是在req结束之前就开始代理了,而加了判断之后,代理服务器是在一个同步动作之后(then),中间有个时间延迟,这个延迟完全导致在代理发生之前req就结束了。那么代理也就失去了接受原始req的机会。
其他
在搜索解决方案的时候,发现了一个类似的场景,导致类似的问题,就是body-parser如果在http-proxy之前,也会导致这个问题。具体就请参考这里 以及 这里.